/**
 * @ Author: 紫衣
 * @ Create Time: 2022-12-26 20:40:32
 * @ Email: 15227974559@163.com
 * @ Modified by: --
 * @ Modified time: 2023-01-04 17:23:32
 * @ Description: 图片裁剪工具 | 头像生成器
 */

const promiseGetImage = (file) =>
  new Promise((resolve, reject) => {
    try {
      const reader = new FileReader()
      reader.readAsDataURL(file)
      reader.onloadend = function (e) {
        // 页面回显选中的图片
        // imageWrap.src = reader.result
        const image = new Image()
        image.src = reader.result
        image.onload = () => {
          console.log('width:', image.width)
          console.log('height:', image.height)
          return resolve({
            image: reader.result,
            height: image.height,
            width: image.width,
          })
        }
      }
    } catch (error) {
      return reject(error)
    }
  })

// 封装 CutImage 功能类
class CutImage {
  constructor(id, config) {
    // 容器
    this.$wrap = document.getElementById(id)
    this.$bakWrap = document.getElementById('showWrap')

    this.cv = null
    this.ctx = null
    this.bakCv = null
    this.bakCtx = null

    // 缩放量
    this.ZOOM = 1

    // 图片真实大小
    this.realSize = {
      width: 0,
      height: 0,
    }

    // 视图图片数据
    this.viewSize = {
      x: 0,
      y: 0,
      width: 0,
      height: 0,
    }

    // 偏移量
    this.offsetSize = {
      x: 0,
      y: 0,
    }

    // 截图区域数据
    this.paneSize = {
      x: 200,
      y: 100,
      width: 200,
      height: 200,
    }

    // 拖拽之前的备份数据
    this.copyLastViewSize = {}

    this.setConfig(config)
    this.init()
  }

  init() {
    this.initCanvas()
    this.initBackCanvas()
    this.draw()
    this.bindEvent()
  }

  // 初始化画布
  initCanvas() {
    const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d')

    canvas.height = this.config.height
    canvas.width = this.config.width
    canvas.style.background = this.config.backColor

    this.$wrap.appendChild(canvas)

    this.cv = canvas
    this.ctx = ctx
  }

  initBackCanvas() {
    this.$bakWrap.innerHTML = ''

    const canvas = document.createElement('canvas')
    const ctx = canvas.getContext('2d')

    canvas.height = this.paneSize.height
    canvas.width = this.paneSize.width

    canvas.style.background = '#eee'
    canvas.style.position = 'absolute'
    canvas.style.top =
      (this.$wrap.clientHeight - canvas.height) / 2 + 20 + 1 + 'px'
    canvas.style.left =
      (this.$wrap.clientWidth - canvas.width) / 2 - 20 - 1 + 'px'

    this.$bakWrap.appendChild(canvas)

    this.bakCv = canvas
    this.bakCtx = ctx
  }

  /**
   * 绘制回显图形
   */
  drawBackImage() {
    return new Promise((resolve, reject) => {
      try {
        const clipImageData = this.ctx.getImageData(
          this.paneSize.x,
          this.paneSize.y,
          this.paneSize.width,
          this.paneSize.height
        )
        this.bakCtx.clearRect(0, 0, this.paneSize.width, this.paneSize.height)
        this.bakCtx.putImageData(clipImageData, 0, 0)
        return resolve('ok')
        // }.bind(this)
      } catch (error) {
        return reject(error)
      }
    })
  }

  /**
   * 绘制图片
   * 为了解耦，把绘制图片第几个关键数据都放在 this.viewSize 上，使本方法变成数据驱动
   */
  drawImage() {
    return new Promise((resolve, reject) => {
      try {
        const image = new Image()
        image.src = this.image.image
        // image.onload = function () {
        this.ctx.save()
        this.ctx.drawImage(
          image,
          this.viewSize.x,
          this.viewSize.y,
          this.viewSize.width,
          this.viewSize.height
        )
        this.ctx.save()
        console.log('绘制图片')
        return resolve('ok')
        // }.bind(this)
      } catch (error) {
        return reject(error)
      }
    })
  }

  // globalCompositeOperation API 使用还是有问题，可能是不适用当前这个场景吧
  // drawPane() {
  //   return new Promise((resolve, reject) => {
  //     try {
  //       console.log('绘制方框')
  //       // 四个角
  //       const point = {
  //         leftTop: { x: this.paneSize.x, y: this.paneSize.y },
  //         rightTop: {
  //           x: this.paneSize.x + this.paneSize.width,
  //           y: this.paneSize.y,
  //         },
  //         rightBottom: {
  //           x: this.paneSize.x + this.paneSize.width,
  //           y: this.paneSize.y + this.paneSize.height,
  //         },
  //         leftBottom: {
  //           x: this.paneSize.x,
  //           y: this.paneSize.y + this.paneSize.height,
  //         },
  //       }

  //       // 小方块
  //       this.ctx.globalCompositeOperation = 'destination-out'
  //       this.ctx.fillStyle = 'rgba(0,0,0,1)'
  //       this.ctx.fillRect(
  //         this.paneSize.x,
  //         this.paneSize.y,
  //         this.paneSize.width,
  //         this.paneSize.height
  //       )

  //       // 大方块
  //       this.ctx.globalCompositeOperation = 'xor'
  //       this.ctx.fillStyle = 'rgba(1,1,1,.3)'
  //       this.ctx.fillRect(0, 0, this.config.width, this.config.height)

  //       return resolve('ok')
  //     } catch (error) {
  //       return reject(error)
  //     }
  //   })
  // }

  /**
   * 绘制方框
   * 内部透明，其他部分半透明
   */
  drawPane() {
    // 四个角
    const point = {
      leftTop: { x: this.paneSize.x, y: this.paneSize.y },
      rightTop: {
        x: this.paneSize.x + this.paneSize.width,
        y: this.paneSize.y,
      },
      rightBottom: {
        x: this.paneSize.x + this.paneSize.width,
        y: this.paneSize.y + this.paneSize.height,
      },
      leftBottom: {
        x: this.paneSize.x,
        y: this.paneSize.y + this.paneSize.height,
      },
    }

    // 四个方块组成阴影部分
    this.ctx.fillStyle = 'rgba(1,1,1,.5)'
    this.ctx.shadowBlur = 0
    this.ctx.fillRect(0, 0, point.leftTop.x, this.config.height)
    this.ctx.fillRect(point.leftTop.x, 0, this.config.width, point.leftTop.y)
    this.ctx.fillRect(
      point.rightTop.x,
      point.rightTop.y,
      this.config.width - point.rightTop.x,
      this.config.height - point.leftTop.y
    )
    this.ctx.fillRect(
      point.leftBottom.x,
      point.leftBottom.y,
      this.paneSize.width,
      this.config.height - point.leftBottom.y
    )

    // 小矩形高亮边框
    this.ctx.strokeStyle = '#fff'
    this.ctx.lineWidth = 0.5
    this.ctx.strokeRect(
      this.paneSize.x,
      this.paneSize.y,
      this.paneSize.width,
      this.paneSize.height
    )

    // 四个角的短线
    let pointLine =
      this.paneSize.width < this.paneSize.height
        ? this.paneSize.width * 0.15
        : this.paneSize.height * 0.15
    this.ctx.beginPath()
    this.ctx.lineWidth = 4
    this.ctx.strokeStyle = 'rgb(134,189,119)'
    this.ctx.shadowColor = 'gray'
    this.ctx.shadowOffsetX = 0
    this.ctx.shadowOffsetY = 0
    this.ctx.shadowBlur = 10
    this.ctx.lineCap = 'round'
    // 左上角
    this.ctx.moveTo(point.leftTop.x, point.leftTop.y)
    this.ctx.lineTo(point.leftTop.x, point.leftTop.y + pointLine)
    this.ctx.moveTo(point.leftTop.x, point.leftTop.y)
    this.ctx.lineTo(point.leftTop.x + pointLine, point.leftTop.y)

    // 右上角
    this.ctx.moveTo(point.rightTop.x, point.rightTop.y)
    this.ctx.lineTo(point.rightTop.x - pointLine, point.rightTop.y)
    this.ctx.moveTo(point.rightTop.x, point.rightTop.y)
    this.ctx.lineTo(point.rightTop.x, point.rightTop.y + pointLine)

    // 右下角
    this.ctx.moveTo(point.rightBottom.x, point.rightBottom.y)
    this.ctx.lineTo(point.rightBottom.x - pointLine, point.rightBottom.y)
    this.ctx.moveTo(point.rightBottom.x, point.rightBottom.y)
    this.ctx.lineTo(point.rightBottom.x, point.rightBottom.y - pointLine)

    // 左下角
    this.ctx.moveTo(point.leftBottom.x, point.leftBottom.y)
    this.ctx.lineTo(point.leftBottom.x + pointLine, point.leftBottom.y)
    this.ctx.moveTo(point.leftBottom.x, point.leftBottom.y)
    this.ctx.lineTo(point.leftBottom.x, point.leftBottom.y - pointLine)

    this.ctx.stroke()
    this.ctx.closePath()
  }

  // 清空画布
  clearCanvas() {
    this.ctx.clearRect(0, 0, this.config.width, this.config.height)
  }

  /**
   * 重置画布，每次拖拽、滚轮事件之后修改数据
   * 数据发生变化以后调用 draw 方法依次重新绘制视图
   */
  async draw() {
    this.clearCanvas()
    this.image && (await this.drawImage())
    await this.drawPane()
    this.image && (await this.drawBackImage())
  }

  /**
   * 设置图片
   * @param {file{image:String, width:Number, height:Number}} image promiseGetImage 方法的resolve数据
   * 需要重置一些数据
   * - 图片对象
   * - 滚轮控制的临时缩放比例
   * - 图片的真实宽高
   * - 图片的视图宽高
   */
  setImage(image) {
    this.image = image
    this.ZOOM = 1
    this.countViewSize('new')
  }

  /**
   * 设置截图区域大小
   */
  setPaneSize() {
    let [x, y] = this.config.clipType.split(':')

    x = Number(x)
    y = Number(y)

    let zoom = x / y

    let paneX, paneY, paneW, paneH
    let clipZoom = 1.5
    if (x > y) {
      let longer = this.config.width / clipZoom
      paneW = longer
      paneH = longer / zoom
    } else if (x < y) {
      let longer = this.config.height / clipZoom
      paneW = longer * zoom
      paneH = longer
    } else {
      let longer =
        this.config.width <= this.config.height
          ? this.config.width / clipZoom
          : this.config.height / clipZoom
      paneW = paneH = longer
    }

    paneX = (this.config.width - paneW) / 2
    paneY = (this.config.height - paneH) / 2

    this.paneSize = {
      x: parseInt(paneX),
      y: parseInt(paneY),
      width: parseInt(paneW),
      height: parseInt(paneH),
    }

    console.log(this.paneSize)
  }

  /**
   * 设置配置
   * @param {*} config 配置
   */
  setConfig(config = {}) {
    const baseConfig = {
      height: this.$wrap.clientHeight, // 高度
      width: this.$wrap.clientWidth, // 宽度
      backColor: '#fff', // 背景颜色
      wheelStep: 0.05, // 滚轮步长
      clipType: '1:1',
    }

    // 合并参数
    this.config = Object.assign({}, baseConfig, config)
    console.log('setConfig :', this.config)

    this.setPaneSize()
    this.initBackCanvas()
    // 因为初始化中会调用一次 setConfig，此时还没有生成 cv 和 ctx 属性，控制台会报错
    this.ctx && this.draw()
  }

  /**
   * 绑定交互事件
   */
  bindEvent() {
    // 绑定滚轮事件，通过滚轮缩放图片
    this.$wrap.addEventListener(
      'mousewheel',
      function (e) {
        e.preventDefault() // 阻止冒泡

        this.copyLastViewSize = JSON.parse(JSON.stringify(this.viewSize))

        if (e.wheelDelta > 0) {
          this.ZOOM += this.config.wheelStep
          this.countViewSize('zoom')
        } else {
          if (this.ZOOM >= 0.3) {
            this.ZOOM -= this.config.wheelStep
            this.countViewSize('zoom')
          }
        }
      }.bind(this) // 绑定 this，修改内部方法的this指向
    )

    /**
     * 三个事件完成拖拽功能
     *  - 鼠标按下：指针切换成十字，并且把拖拽状态打开，记录当前初始位置
     *  - 鼠标松开：指针切换成箭头，并且把拖拽状态关闭
     *  - 鼠标移动：计算鼠标偏移量，计算图片最新位置，刷新视图效果
     * 容错处理：
     *  - 鼠标移出 wrap，移除拖拽效果
     *  - 图片移动超出可视范围，拖拽失效
     */
    let isDrag = false
    let mouseStart = { x: 0, y: 0 }
    // 绑定鼠标按下事件
    this.$wrap.addEventListener(
      'mousedown',
      function (e) {
        console.log('mousedown')
        e.preventDefault()
        // 判断鼠标是否在图片内部
        if (
          e.offsetX > this.viewSize.x &&
          e.offsetX < this.viewSize.x + this.viewSize.width &&
          e.offsetY > this.viewSize.y &&
          e.offsetY < this.viewSize.y + this.viewSize.height
        ) {
          isDrag = true
          this.$wrap.style.cursor = 'move'
          this.copyLastViewSize = JSON.parse(JSON.stringify(this.viewSize))

          // 记录鼠标起始位置
          mouseStart = {
            x: e.offsetX,
            y: e.offsetY,
          }
        }
      }.bind(this)
    )

    // 绑定鼠标松开事件
    this.$wrap.addEventListener(
      'mouseup',
      function (e) {
        console.log('mouseup')
        e.preventDefault()

        isDrag = false
        this.$wrap.style.cursor = 'default'
      }.bind(this)
    )

    // 绑定鼠标移动事件
    this.$wrap.addEventListener(
      'mousemove',
      function (e) {
        e.preventDefault()
        if (isDrag) {
          this.offsetSize = {
            x: e.offsetX - mouseStart.x,
            y: e.offsetY - mouseStart.y,
          }
          this.countViewSize('drag')
        }
      }.bind(this)
    )

    // 绑定鼠标移出事件
    this.$wrap.addEventListener(
      'mouseout',
      function (e) {
        if (isDrag) {
          console.log('mouseout')
          e.preventDefault()

          isDrag = false
          this.$wrap.style.cursor = 'default'
        }
      }.bind(this)
    )
  }

  /**
   * 计算图片视图数据，用来绘制视图
   * 结尾出调用 draw 方法，重绘视图
   * */
  countViewSize(type) {
    if (!this.image) {
      return false
    }

    let viewX, viewY, viewW, viewH, realW, realH

    // 图片宽高
    viewW = this.realSize.width * this.ZOOM
    viewH = this.realSize.height * this.ZOOM

    if (type === 'drag') {
      /* 拖放的数据处理 */
      viewX = this.copyLastViewSize.x + this.offsetSize.x
      viewY = this.copyLastViewSize.y + this.offsetSize.y
    } else if (type === 'zoom') {
      /* 缩放的操作 */

      // 基于左上角缩放
      // viewX = this.copyLastViewSize.x ;
      // viewY = this.copyLastViewSize.y;

      // 基于中心点缩放
      viewX =
        this.copyLastViewSize.x + (this.copyLastViewSize.width - viewW) / 2
      viewY =
        this.copyLastViewSize.y + (this.copyLastViewSize.height - viewH) / 2
    } else if (type === 'new') {
      /* 新创建的操作 */
      let zoom = 1

      // 根据宽高判断缩放宽/高
      this.image.height >= this.image.width
        ? (zoom = this.config.height / this.image.height)
        : (zoom = this.config.width / this.image.width)

      // 真实高度
      realW = this.image.width * zoom
      realH = this.image.height * zoom

      console.log(realW, realH)

      // 计算开始位置
      if (this.image.height >= this.image.width) {
        viewX = (this.config.width - realW) / 2
        viewY = 0
      } else {
        viewX = 0
        viewY = (this.config.height - realH) / 2
      }

      // 原始尺寸的理论数据
      this.realSize = {
        x: viewX,
        y: viewY,
        width: realW,
        height: realH,
        centerX: viewX + realW / 2,
        centerY: viewY + realH / 2,
      }
      viewW = realW
      viewH = realH
    }
    console.log('viewW :', viewW)
    // 视图尺寸
    this.viewSize = {
      x: viewX,
      y: viewY,
      width: viewW,
      height: viewH,
      centerX: viewX + this.offsetSize.x + viewW / 2,
      centerY: viewY + this.offsetSize.y + viewH / 2,
    }

    console.log(this.viewSize)

    // 重绘视图
    this.draw()
  }

  saveAsImage() {
    if (!this.image) {
      console.error('还没有图片，怎么下载？')
      return false
    }
    const url = this.bakCv.toDataURL('image/png')
    const oA = document.createElement('a')
    oA.download = '下载' // 设置下载的文件名，默认是'下载'
    oA.href = url
    document.body.appendChild(oA)
    oA.click()
    oA.remove()
  }
}

/**
 * 对外暴露一个工具方法，供主流程使用
 * @param {Number} id dom id
 * @param {any} config 支持修改的配置项
 * @returns Object<CutImage>
 */
const createCutImage = (id, config) => {
  return new CutImage(id, config)
}

// const cutWrap = createCutImage('cutWrap')
// // const imageWrap = document.getElementById('imgShow')

// const changeFile = async (e) => {
//   e = e || event
//   const file = e.target.files[0]
//   const image = await promiseGetImage(file)
//   cutWrap.setImage(image)
// }

// const choiceImageButton = document.getElementById('choiceImage')
// choiceImageButton.addEventListener('click', function () {
//   document.getElementById('uploadFile').click()
// })

// const resetCanvasButton = document.getElementById('resetCanvas')
// resetCanvasButton.addEventListener('click', function () {
//   const valPaneWidth = document.getElementById('paneWidth').value || 1
//   const valPaneHeight = document.getElementById('paneHeight').value || 1

//   const _config = {
//     clipType: valPaneWidth + ':' + valPaneHeight,
//   }

//   cutWrap.setConfig(_config)
// })

// const saveAsImageButton = document.getElementById('saveAsImage')
// saveAsImageButton.addEventListener('click', function () {
//   cutWrap.saveAsImage()
// })

/**
 * 有效整合一个一体化的裁剪工具
 * 1- 纯 js 添加表单项
 *  a- input:file 美化初始状态，点击 + 触发选择图片
 *  b- input:text 修改裁剪区域比例
 * 2- 回显区域的配置
 * 3- 按钮
 *  a- 保存为 jpg
 *  b- 上传
 * 4- UI 美化
 */

// TODO globalCompositeOperation 的测试
